//参考mongoose网址:  http://www.nodeclass.com/api/mongoose.html#query_Query-exec
// 参考mongodb网址:  https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/
// ObjectId()网址 :  http://blog.csdn.net/xiamizy/article/details/41521025
const Promise = require('bluebird');
const mongoose = require('mongoose');
const TestModel = require('./model');
const Class = require('./class');
const Student = require('./student');

mongoose.Promise = Promise; //引用bluebird替换mongoose的promise
mongoose.connect('mongodb://10.10.10.16:27017/test');
const connection = mongoose.connection;
connection.on('error', error => { // 数据库连接失败
    console.log('链接错误:' + error)
});

// console.log(mongoose.Types.ObjectId())
connection.once('open', () => {
    console.log('连接成功');
    return connection.dropDatabase()
        .then(data => {
            console.log('清除数据库')
            //  插入数据:create({});
            //  批量插入数据: create([{]}]) / insertMany([{}])
            return TestModel.insertMany([
                {
                    title: '标题1',
                    contents: "内容1",
                    comments: [
                        {
                            author: 'qqq1',
                            comment: '好',
                            score: 5
                        },
                        {
                            author: 'qqq2',
                            comment: '很好',
                            score: 5
                        },
                        {
                            author: 'qqq3',
                            comment: '非常好',
                            score: 5
                        }
                    ],
                    score: 15
                },
                {
                    title: '标题2',
                    contents: "内容2",
                    comments: [
                        {
                            author: 'zzz1',
                            comment: '好',
                            score: 5
                        },
                        {
                            author: 'zzz2',
                            comment: '很好',
                            score: 5
                        },
                        {
                            author: 'zzz3',
                            comment: '非常好',
                            score: 10
                        }
                    ],
                    score: 25
                },
            ])
        })
        .then(data => {
            console.log('insertMany成功:\n', data)
            //    更新数据:update();
            //    使用$push/$addToSet配合$each可以向数组中添加多个元素;$push还可以配合$slice:整数
            return TestModel.update(
                { title: '标题2' },
                {
                    $push: {
                        comments: {
                            $each: [
                                {
                                    author: 'zzz3',
                                    comment: '非常好',
                                    score: 10
                                },
                                {
                                    author: 'zzz4',
                                    comment: '非常好',
                                    score: 10
                                },
                                {
                                    author: 'zzz5',
                                    comment: '非常好',
                                    score: 10
                                }
                            ],
                            $slice: -4
                        }
                    },
                    $unset: { placeholder: 1 }
                }
            )
        })
        .then(data => {
            console.log('update/$push/$each/$slice成功:\n', data);
            //addToSet在数组中添加不重复的数值
            return TestModel.update(
                { title: '标题1' },
                {
                    $addToSet: {
                        comments: {
                            $each: [
                                {
                                    author: 'qqq4',
                                    comment: '非常好',
                                    score: 10
                                },
                                {
                                    author: 'zzz5',
                                    comment: '非常好',
                                    score: 10
                                },
                                {
                                    author: 'qqq4',
                                    comment: '非常好'
                                },
                            ],
                            $slice: -4
                        }
                    },
                    $unset: { placeholder: 1 }
                }
            )
        })
        .then(data => {
            console.log('update/$addToSet/$each成功:\n', data);
            //配合$位置修改器 更改数组中的某一个值
            return TestModel.update(
                { 'comments.author': 'zzz5', title: '标题1' },
                {
                    $set: { 'comments.$.author': 'qqq5' },
                    $unset: { placeholder: 1 }
                }
                // { multi: true }
            )
        })
        .then(data => {
            console.log('update/$成功:\n', data);
            //自增修改器$inc:{filed:整数}
            return TestModel.update(
                { title: '标题1' },
                {
                    $inc: { score: 1 },
                    $unset: { placeholder: 1 }
                }
                // ,{ multi: true } //更新多个文档
            )
        })
        .then(data => {
            console.log('update/$inc成功:\n', data);
            //update第三参数添加upsertshu属性为true,更新时,如果找不到会创建(没有默认值),否则,不会有任何更新;如果找到就是正常更新
            return TestModel.update(
                { title: '标题3' },
                {
                    contents: "内容2",
                    comments: [
                        {
                            author: 'yyy1',
                            comment: '好'
                        },
                        {
                            author: 'yyy2',
                            comment: '很好'
                        },
                        {
                            author: 'yyy3',
                            comment: '非常好'
                        }
                    ],
                },
                { upsert: true }
            )
        })
        .then(data => {
            console.log('update/upsert成功:\n', data);
            return TestModel.update(
                { title: '标题3' },
                {
                    contents: "内容3",
                    comments: [
                        {
                            author: 'yyy1',
                            comment: '好'
                        },
                        {
                            author: 'yyy2',
                            comment: '很好'
                        },
                        {
                            author: 'yyy2',
                            comment: '很好'
                        },
                        {
                            author: 'yyy2',
                            comment: '很好'
                        },
                        {
                            author: 'yyy3',
                            comment: '非常好'
                        }
                    ],
                    $set: { score: 15 },
                    $unset: { placeholder: 1 }
                },
                { upsert: 1 }
            )
        })
        .then(data => {
            console.log('update/upsert成功:\n', data);
            //使用正则表达式
            return TestModel.find(
                {
                    title: /标题2|标题3/,
                    score: { $gte: 15 }
                },
                {
                    _id: 0,
                    title: 1,
                    contents: 1,
                    comments: 1,
                    score: 1
                }
            )
        })
        .then(data => {
            console.log('reg/查找成功:\n', data);
            //$or匹配两个不同字段任意一个条件;
            return TestModel.find(
                {
                    $or: [{ title: '标题1' }, { score: { $gte: 15 } }]
                },
                {
                    _id: 0,
                    title: 1,
                    contents: 1,
                    comments: 1,
                    score: 1
                }
            )
        })
        .then(data => {
            console.log('$or查询成功:\n' + data);
            // $in 匹配同一个字段任意条件
            return TestModel.find(
                {
                    title: { $in: ['标题2', '标题3'] }
                },
                {
                    _id: 0,
                    title: 1,
                    contents: 1,
                    comments: 1,
                    score: 1
                }
            )
        })
        .then(data => {
            console.log('$in查找成功:\n' + data);
            //当返回的是model实例时;
            //保留原有的大部分API;
            //调用toString方法返回的是model._doc;
            //model.已定义字段名=值 <=> model._doc.已定义字段名=值
            //model.未定义字段名=值 <=> model.未定义字段名=值
            return TestModel.findOne(
                {
                    title: '标题3'
                },
                {
                    _id: 0,
                    title: 1,
                    contents: 1,
                    comments: 1,
                    score: 1
                }
            )
        })
        .then(data => {
            data.verify = true;
            data.score = 20;
            //调用lean()后返回值将变成普通的对象,不再拥有model的API;
            console.log('添加属性1:\n', data);
            return TestModel.findOne(
                {
                    title: '标题3'
                },
                {
                    _id: 0,
                    title: 1,
                    contents: 1,
                    comments: 1,
                    score: 1
                }
            ).lean(true)
        })
        .then(data => {
            data.verify = true;
            data.score = 20;
            console.log('添加属性2:\n', data);
            return TestModel.findOne(
                {
                    title: '标题3'
                }
            )
        })
        .then(data => {
            data._doc.verify = true;
            data.score = [5, 25];
            console.log('添加属性3:\n', data);
            // findOne如果使用了第二参数,返回的mondel保存时会报错;
            return data.save()
        })
        .then(data => {
            console.log('保存属性:\n', data);
            /*
            $where:function(){ js代码 } 
            注意事项:
                1.效率很低,, MongoDB需要把文档转成json然后执行js逻辑;
                2.外层不能使用箭头函数;不然this所指上下文会不正确;
                3.this指代的是游标当前位置的文档;
            */
            return TestModel.find(
                {
                    '$where': function () {
                        if (this.comments.length == 4) {
                            return true
                        } else {
                            return false
                        }
                    }
                }
            )
        })
        .then(data => {
            console.log('$where成功:\n', data)
            // 如果字段是一个混合类型时要注意;数组的匹配;
            // 25<?<20 理论上是匹配不到文档;
            // 实际上mongodb执行的是两个查询然后将结果取交集;
            return TestModel.find({
                score: { $gte: 25, $lte: 20 }
            })
        })
        .then(data => {
            // 示例中5<=20 && 25>=25 所以会匹配到该文档
            console.log('数组查询1:', data)
            //采用$elemMatch对数组中的每个元素进行匹配;注:只能对数组进行匹配;如果这个字段不是数组,将匹配不成功 
            return TestModel.find({ score: { $elemMatch: { $gte: 5, $lte: 20 } } }) //score:[16,18] √     score:15 ×
        })
        .then(data => {
            console.log('$elemMatch成功:\n', data)
            // return TestModel.find({}, {}, { skip: 1, limit: 2 })
            // skip进行游标操作时, 不要跳过大量的文档,这样会很慢;因为MongoDB实际上是一个一个找到后在过滤掉的;
            return TestModel.find().skip(1).limit(2)
        })
        .then(data => {
            console.log('skip/limit成功:\n', data)
            //查询值为null的字段;MongoDB中null==null为true;不存在的字段也为null;
            return TestModel.find({ attr: null })
        })
        .then(data => {
            console.log('null查询成功:\n', data)
            //查询值为null且存在时;
            return TestModel.find({ attr: { $in: [null], $exists: true } })
        })
        .then(data => {
            console.log('$exists/null查询成功:\n', data)
            //插入数据
            return TestModel.update(
                { title: '标题4' },
                {
                    contents: '内容5',
                    comments: [
                        {
                            author: 'qqq1',
                            comment: '好',
                            score: 5
                        }
                    ],
                    score: ['a', 'b', 'c']
                },
                { upsert: 1 }
            )
        })
        .then(data => {
            console.log('upsert插入成功:\n', data)
            return TestModel.create(
                {
                    title: '标题5',
                    contents: '内容5',
                    comments: [
                        {
                            author: 'qqq1',
                            comment: '好',
                            score: 5
                        }
                    ],
                    score: ['a', 'b', 'd']
                }
            )
        })
        .then(data => {
            console.log('create创建成功:\n', data)
            //针对数组查询
            //示例中的写法等价于$in;匹配时数组中有一个元素在其中,就满足条件;
            return TestModel.find({ score: ['c', 'd'] })
        })
        .then(data => {
            console.log('查询1:\n', data)
            //匹配时数组中满足两个条件才能匹配;
            return TestModel.find({ score: { $all: ['c', 'd'] } })
        })
        .then(data => {
            console.log('查询2:\n', data)
            //匹配时数组中满足两个条件才能匹配;
            return TestModel.find({ score: { $all: ['a', 'd'] } })
        })
        .then(data => {
            console.log('查询3:\n', data)
            // find的第二参数可以配合使用$slice对数组进行操作;
            // 示例:跳过前面2个取4个;
            return TestModel.findOne({ title: '标题1' }, { comments: { $slice: [2, 4] } })
        })
        .then(data => {
            console.log('查询4:\n', data)
            return TestModel.distinct('contents')
        })
        .then(data => {
            console.log('查询5:\n', data)
            //聚合:通过管道进行筛选/投射/分组/排序/限制/跳过
            //管道操作的数据保存在内存中;
            return TestModel.aggregate([
                {
                    $unwind: '$comments'
                }
            ])
        })
        .then(data => {
            console.log('管道操作1:\n', data)
            return TestModel.aggregate([
                {
                    $project: { 标题: '$title', 评论: '$comments' }
                }
            ])
        })
        .then(data => {
            console.log('管道操作2:\n', data)
            return TestModel.aggregate([
                {
                    $group: {
                        _id: '$contents',
                        标题: { $addToSet: "$title" }
                    }
                }
            ])
        })
        .then(data => {
            console.log('管道操作3:\n', data)
            return TestModel.aggregate([
                {
                    $match: {
                        title: { $in: ['标题2', '标题3', '标题4', '标题5'] }
                    }
                },
                {
                    $unwind: '$comments'
                },
                {
                    $project: {
                        标题: { $concat: ['这是标题:', { $substr: ['$title', 6, 4] }] },//$substr以字节为单位!!!
                        内容: '$contents',
                        评论: '$comments',
                        评分: { $cond: ['$comments.score', '$comments.score', 0] }
                    }
                },
                {
                    $group: {
                        _id: '$标题',
                        score: { $sum: '$评分' },
                        contents: { $addToSet: '$内容' }
                    }
                }
            ])
        })
        .then(data => {
            console.log('管道操作4:\n', data)
            return Promise.all([
                Class.insertMany([
                    {
                        class: '一年级',
                        teacher: '刘老师',
                        course: ['一年级语文', '一年级数学', '一年级自然']
                    },
                    {
                        class: '二年级',
                        teacher: '李老师',
                        course: ['二年级语文', '二年级数学', '二年级自然']
                    },
                    {
                        class: '三年级',
                        teacher: '伍老师',
                        course: ['三年级语文', '三年级数学', '三年级自然']
                    },
                ]),
                Student.insertMany([
                    {
                        name: '小花',
                        age: 6,
                        hobby: ['跳舞', '唱歌'],
                        class: '一年级'
                    },
                    {
                        name: '小明',
                        age: 7,
                        hobby: ['游泳'],
                        class: '二年级'
                    },
                ])
            ])
        })
        .then(data => {
            return Student.aggregate([
                {
                    $lookup:
                        {
                            from: "classes",
                            localField: "class",
                            foreignField: "class",
                            as: "myClass"
                        }
                }
            ])
        })
        .then(data => {
            console.log('管道操作5:\n', data)
        })
        .catch(err => {
            console.log('发生错误', err)
        })
})
